package com.sylli.oeuf.server.service;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sylli.oeuf.server.db.sql.MySQLConnector;
import com.sylli.oeuf.server.exception.BasedException;
import com.sylli.oeuf.server.exception.ExceptionCause;
import com.sylli.oeuf.server.game.logic.Battle;
import com.sylli.oeuf.server.game.logic.BattleActor;
import com.sylli.oeuf.server.game.logic.LevelLogic;
import com.sylli.oeuf.server.game.logic.Leveling;
import com.sylli.oeuf.server.game.logic.invocation.LogicObjectFactory;
import com.sylli.oeuf.server.object.transferable.CharacterGeneralInfoTO;
import com.sylli.oeuf.server.object.transferable.CharacterPostDataTO;
import com.sylli.oeuf.server.object.transferable.MapBlockTO;
import com.sylli.oeuf.server.object.transferable.MonsterTO;
import com.sylli.oeuf.server.object.transferable.PostBattleResponseTO;
import com.sylli.oeuf.server.object.transferable.PostNPCConversationLogicTO;
import com.sylli.oeuf.server.object.transferable.PostResponseTO;
import com.sylli.oeuf.server.object.transferable.WrappedCharacterInfoTO;

public class GeneralService extends BasedService {
	
	/**
	 * Most of all service required an authentication from mainApplication. Which client would recviev on connection.
	 */
	
	private static Logger log = LoggerFactory.getLogger( "service.general" );
	
	private static int callCount = 0;
	
	public GeneralService() {
		callCount++;
		serviceName = "general";
		log.info("General service created. (" + callCount + ")");
	}
	
	public List<String> dev_getSkillList() {
		return new ArrayList<String>(LogicObjectFactory.getSkillNames());
	}
	
	public List<Long> advanceQuery(long clientId, String queryStatement) throws BasedException {
		/* add verification feature upon the directQuery function */
		if(verifyService(clientId)) {
			try {
				ResultSet rs = directQuery(queryStatement);
				List<Long> ll = new ArrayList<Long>();
				int i= 0;
				for(rs.beforeFirst();rs.next();i++) {
					ll.add(rs.getLong(1));
				}
				return ll;
			} catch (SQLException e) {
				log.error("Error during browsing query result.", e);
				throw new BasedException("Browsing Query throws error", e);
			}
		} else {
			throw new BasedException(ExceptionCause.AssertionFail, "Service is invalid (clientId = " + clientId + ")");
		}
	}
	
	protected ResultSet directQuery(String queryStatement) throws BasedException {
		try {
			return MySQLConnector.getInstance().sqlQuery(queryStatement);
		} catch (SQLException e) {
			log.error("Error during query the database", e);
			throw new BasedException("Direct Query throws error", e);
		}
	}
	
	public String keepInTouch(long clientId) {
		// do nothing.
		return "hello";
	}
	
	public WrappedCharacterInfoTO getWrappedCharacterInfo() throws BasedException {
		return sessionManager.getWrappedCharacterInfo();
	}

	public PostBattleResponseTO postBattleResponse(int monsterId, int actionStrategy) {
		PostBattleResponseTO _resp = new PostBattleResponseTO();
		try {
			/**
			 * 
			 * MUST REWRITE THIS FUNCTION IN CASE OF
			 * THE MORE ADVANCE BATTLE, WHICH ENABLES 
			 * DRAWS OR ATTACKER LOSE BECAUSE DoT.
			 */
			/* get strategy from table */
			/* get monster from table */
			MonsterTO m = providers.monster.get("monster_id = " + monsterId);
			WrappedCharacterInfoTO player = getWrappedCharacterInfo();
			
			BattleActor a = new BattleActor(player);
			BattleActor b = new BattleActor(m);
			
			a.setUsingPlan(player.findPlan(actionStrategy));
			b.setUsingPlan(m.findPlan((int) (m.getAvailablePlanCount() * Math.random())));
			
			Battle battle = new Battle(a, b);
			battle.startBattle(_resp);
			Leveling logic = new LevelLogic();
			if (_resp.getFlag()==PostResponseTO.POSTRESP_BATTLE_WIN) {
				player.getPostData().setCurrentExp(player.getPostData().getCurrentExp() + m.getExp());
				_resp.addAchievemntMessage(player.getGeneralInfo().getName() + " earned " + m.getExp() + " experiences.");
				if (player.getPostData().getCurrentExp() > player.getGeneralInfo().getMaxExp()) {
					// upgrade return flag.
					_resp.setFlag(PostResponseTO.POSTRESP_BATTLE_WIN_GAIN_LEVEL);
					// get data ready.
					
					CharacterGeneralInfoTO chargen = player.getGeneralInfo();
					int leftOver = player.getPostData().getCurrentExp();
					int targetLevel = chargen.getLevel();
					do {
						leftOver -= chargen.getMaxExp();
						
						targetLevel++;
						chargen.setLevel(targetLevel);
						chargen.setMaxExp(logic.getMaxExpForLevel(targetLevel));
						// award skill point & stat point.
						chargen.setLevelUp(chargen.getLevelUp().add(logic.getRewardForGainLevel(targetLevel)));
					} while (leftOver > chargen.getMaxExp());
					player.getPostData().setCurrentExp(leftOver);
					_resp.addAchievemntMessage(player.getGeneralInfo().getName() + " is now level " + player.getGeneralInfo().getLevel() + ".");
					// set it back
					providers.charGenInfo.update(player.getGeneralInfo());
				}
			} else {
				// dead penalty ..
			}
			_resp.setPostData(player.getPostData());
			providers.charPostData.update(player.getPostData());
			return _resp;
		} catch (BasedException e) {
			log.error("Error: " + e.getMessage());
			_resp.setResponseContent(PostResponseTO.POSTRESP_CRITCAL, 
					"$System Exception: " + e.getMessage());
			return _resp;
		}
	}
	

	public PostNPCConversationLogicTO postActionToNPC(int npc_id, int action_id) {
		// if action_id != 0 then, it's response action from logic statement.
		// else (action_id == 0) then, it's a request for logic statement.
		if (action_id == 0) {
			PostNPCConversationLogicTO pnpc = new PostNPCConversationLogicTO(); 
			pnpc.setupEntites(
					"0%ba%Hello%ba%1,2%be%" + 
					"1%ba%Say hi back. Politely%ba%4%be%" +
					"2%ba%Ignore him and walk pass quickly.%ba%3%be%" +
					"3%ba%Fuck you man+%ba%5,6%be%" +
					"4%ba%You are noob man+, you level is still noob%ba%5%be%" +
					"5%ba%End this conversation.%ba%-1%be%" +
					"6%ba%Run away.%ba%-1%be%");
			
			log.info("Talk to NPC: " + pnpc);
			return pnpc;
		} else {
			return new PostNPCConversationLogicTO();
		}
	}
	
	public PostResponseTO postNewPosition(int new_position) {
		PostResponseTO _resp = new PostResponseTO();
		try {
			CharacterPostDataTO _post = sessionManager.getCharacterPostData();
			if (validateDistance(_post.getCurrentPos(), new_position)) {	// if position is not warping (distance == 1)
				// check new position in map
				int map_id = _post.getCurrentMap();
				
				/*
				 * Validate from map_block.
				 */
				String mapReferenceWhereClause = "map_id = " + map_id + " AND block_id = " + new_position;
				MapBlockTO mapBlock = providers.mapBlock.get(mapReferenceWhereClause);
				
				/**
				 * Consult this page for glory information
				 * URL: http://code.google.com/p/oeuf/wiki/PostDataProtocol
				 */
				_post.setCurrentPos(new_position);
				// check terrain type.
				int t = mapBlock.getTerrainType();
				switch (t) {
				case 1: // just-block;
					_resp.setResponseContent(PostResponseTO.POSTRESP_EXPECTED_ERROR, "$block");
					return _resp;
				case 0: // plain-area 
					break;
				default:
					throw new BasedException("Database is inconsistant, TerrainType is invalid for warp type spot. terrainType=" + t);
				}
				
				// check 
				if (mapBlock.getFlag() == 0) {	// neutral
					_resp.setResponseContent(PostResponseTO.POSTRESP_NOTHING, null);
				} else if ((mapBlock.getFlag() & 1) == 1) {	// 1 battle
					/* random chance to found mob */
					String p = mapBlock.getParam();
					int hitMonsterChance = Integer.parseInt(p);
					if ((int) (Math.random() * 100) <= hitMonsterChance) {
						/* random which mob */
						ResultSet rs = directQuery("SELECT monster_id, spawn_perc FROM map_monster WHERE " 
								+ mapReferenceWhereClause);
						if (rs.next()) {
							float pickMob = (float) (Math.random() * 100);
							boolean done = false;
							int pickedMobId = -14303;
							rs.first(); // move to head
							while (!done) {
								//rs.getInt(1);
								pickMob -= rs.getFloat(2);
								if (pickMob > 0) {
									done = !rs.next();
								} else {
									pickedMobId = rs.getInt(1);
									done = true;
								}
							}
							_resp.setResponseContent(PostResponseTO.POSTRESP_BATTLE, "" + pickedMobId);
						} else {
							/* database cannot find any relavent information about monster in this map */
							throw new BasedException("Database is inconsistant, No monster associated this map_block's entry. where?=" + mapReferenceWhereClause);
						}
					} else {
						/* miss monster just update the moving */
						_resp.setResponseContent(PostResponseTO.POSTRESP_NOTHING, null);
					}
				} else if ((mapBlock.getFlag() & 2) == 2) {	// warp
					/* warp */
					String p = mapBlock.getParam();
					String[] s = p.split(",");
					if (s.length != 2) {
						throw new BasedException("Database is inconsistant, Param is invalid for warp type spot. param=" + p);
					} else {
						_post.setCurrentMap(Integer.parseInt(s[0]));
						_post.setCurrentPos(Integer.parseInt(s[1]));
						// set Response Context
						_resp.setResponseContent(PostResponseTO.POSTRESP_WARP, p);
					}
				} else if ((mapBlock.getFlag() & 4) == 4) {	// 
					/* shop */
					_resp.setResponseContent(PostResponseTO.POSTRESP_SHOP, mapBlock.getParam());
				} else if ((mapBlock.getFlag() & 8) == 8) {	// npc
					/* npc */
					_resp.setResponseContent(PostResponseTO.POSTRESP_NPC, mapBlock.getParam());
					log.info("NPC found: {}, {}", mapBlock.getBlockId(), mapBlock.getParam());
				}
				// update database && put the _resp through
				providers.charPostData.update(_post);
				return _resp;
			} else {
				_resp.setResponseContent(PostResponseTO.POSTRESP_EXPECTED_ERROR, 
						"$Distance Invalid");
				return _resp;
			}
		} catch (Exception e) {
			log.error("Error: " + e.getMessage());
			_resp.setResponseContent(PostResponseTO.POSTRESP_CRITCAL, 
					"$System Exception: " + e.getMessage());
			return _resp;
		}
	}
	
	protected static boolean validateDistance(int old_position, int new_position) {
		return computePolicyDistance(old_position, new_position) == 1;
	}
	
	protected static int computePolicyDistance(int old_position, int new_position) {
		int old_x = old_position / 1000;
		int old_y = old_position % 1000;
		int new_x = new_position / 1000;
		int new_y = new_position % 1000;
		
		return Math.abs(new_x - old_x) + Math.abs(new_y - old_y);
	}
	
}
